{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 직렬화(Serialization)\n",
    "\n",
    "### 직렬화(Serialization) 란?\n",
    "\n",
    "1. **정의:**\n",
    "   - 모델을 저장 가능한 형식으로 변환하는 과정\n",
    "\n",
    "2. **목적:**\n",
    "   - 모델 재사용 (재훈련 없이)\n",
    "   - 모델 배포 및 공유 용이\n",
    "   - 계산 리소스 절약\n",
    "\n",
    "3. **장점:**\n",
    "   - 빠른 모델 로딩\n",
    "   - 버전 관리 가능\n",
    "   - 다양한 환경에서 사용 가능\n",
    "\n",
    "모델 직렬화는 AI 개발 및 배포 과정에서 중요한 단계로, 효율적인 모델 관리와 재사용을 가능하게 합니다.\n",
    "\n",
    "`is_lc_serializable` 클래스 메서드로 실행하여 LangChain 클래스가 직렬화 가능한지 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# API KEY를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API KEY 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH04-Models\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "# 프롬프트 템플릿을 사용하여 질문을 생성합니다.\n",
    "prompt = PromptTemplate.from_template(\"{fruit}의 색상이 무엇입니까?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "클래스(class) 에 대하여 직렬화 가능 여부를 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 직렬화가 가능한지 체크합니다.\n",
    "print(f\"ChatOpenAI: {ChatOpenAI.is_lc_serializable()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "llm 객체에 대하여 직렬화 가능 여부를 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
    "\n",
    "# 직렬화가 가능한지 체크합니다.\n",
    "print(f\"ChatOpenAI: {llm.is_lc_serializable()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 체인을 생성합니다.\n",
    "chain = prompt | llm\n",
    "\n",
    "# 직렬화가 가능한지 체크합니다.\n",
    "chain.is_lc_serializable()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 체인(Chain) 직렬화(dumps, dumpd)\n",
    "\n",
    "### 개요\n",
    "\n",
    "체인 직렬화는 직렬화 가능한 모든 객체를 딕셔너리 또는 JSON 문자열로 변환하는 과정을 의미합니다.\n",
    "\n",
    "### 직렬화 방법\n",
    "\n",
    "객체의 속성 및 데이터를 키-값 쌍으로 저장하여 딕셔너리 형태로 변환합니다.\n",
    "\n",
    "이러한 직렬화 방식은 객체를 쉽게 저장하고 전송할 수 있게 하며, 다양한 환경에서 객체를 재구성할 수 있도록 합니다.\n",
    "\n",
    "**참고**\n",
    "- `dumps`: 객체를 JSON 문자열로 직렬화\n",
    "- `dumpd`: 객체를 딕셔너리로 직렬화\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.load import dumpd, dumps\n",
    "\n",
    "dumpd_chain = dumpd(chain)\n",
    "dumpd_chain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 직렬화된 체인의 타입을 확인합니다.\n",
    "type(dumpd_chain)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이번에는 `dumps` 함수를 사용하여 직렬화된 체인을 확인해보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# dumps 함수를 사용하여 직렬화된 체인을 확인합니다.\n",
    "dumps_chain = dumps(chain)\n",
    "dumps_chain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 직렬화된 체인의 타입을 확인합니다.\n",
    "type(dumps_chain)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pickle 파일\n",
    "\n",
    "### 개요\n",
    "\n",
    "Pickle 파일은 Python 객체를 바이너리 형태로 직렬화하는 포맷입니다.\n",
    "\n",
    "### 특징\n",
    "\n",
    "1. **형식:**\n",
    "   - Python 객체를 바이너리 형태로 직렬화하는 포맷\n",
    "\n",
    "2. **특징:**\n",
    "   - Python 전용 (다른 언어와 호환 불가)\n",
    "   - 대부분의 Python 데이터 타입 지원 (리스트, 딕셔너리, 클래스 등)\n",
    "   - 객체의 상태와 구조를 그대로 보존\n",
    "\n",
    "3. **장점:**\n",
    "   - 효율적인 저장 및 전송\n",
    "   - 복잡한 객체 구조 유지\n",
    "   - 빠른 직렬화/역직렬화 속도\n",
    "\n",
    "4. **단점:**\n",
    "   - 보안 위험 (신뢰할 수 없는 데이터 역직렬화 시 주의 필요)\n",
    "   - 사람이 읽을 수 없는 바이너리 형식\n",
    "\n",
    "### 주요 용도\n",
    "\n",
    "1. 객체 캐싱\n",
    "2. 머신러닝 모델 저장\n",
    "3. 프로그램 상태 저장 및 복원\n",
    "\n",
    "### 사용법\n",
    "\n",
    "- `pickle.dump()`: 객체를 파일에 저장\n",
    "- `pickle.load()`: 파일에서 객체 로드\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pickle 파일로 저장합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "# fuit_chain.pkl 파일로 직렬화된 체인을 저장합니다.\n",
    "with open(\"fruit_chain.pkl\", \"wb\") as f:\n",
    "    pickle.dump(dumpd_chain, f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "JSON 형식으로 마찬가지로 저장할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "with open(\"fruit_chain.json\", \"w\") as fp:\n",
    "    json.dump(dumpd_chain, fp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## load: 저장한 모델 불러오기\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저, 이전에 저장한 `pickle` 형식의 파일을 로드합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "# pickle 파일을 로드합니다.\n",
    "with open(\"fruit_chain.pkl\", \"rb\") as f:\n",
    "    loaded_chain = pickle.load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "로드한 json 파일을 `load` 메서드를 사용하여 로드합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.load import load\n",
    "\n",
    "# 체인을 로드합니다.\n",
    "chain_from_file = load(loaded_chain)\n",
    "\n",
    "# 체인을 실행합니다.\n",
    "print(chain_from_file.invoke({\"fruit\": \"사과\"}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.load import load, loads\n",
    "\n",
    "load_chain = load(\n",
    "    loaded_chain, secrets_map={\"OPENAI_API_KEY\": os.environ[\"OPENAI_API_KEY\"]}\n",
    ")\n",
    "\n",
    "# 불러온 체인이 정상 동작하는지 확인합니다.\n",
    "load_chain.invoke({\"fruit\": \"사과\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"fruit_chain.json\", \"r\") as fp:\n",
    "    loaded_from_json_chain = json.load(fp)\n",
    "    loads_chain = load(loaded_from_json_chain)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 불러온 체인이 정상 동작하는지 확인합니다.\n",
    "loads_chain.invoke({\"fruit\": \"사과\"})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
